﻿using System;
using System.Collections.Generic;
using Kaleida.ServiceMonitor.Framework;
using Kaleida.ServiceMonitor.Model.Runtime;
using Kaleida.ServiceMonitor.Model.StructuredSource;
using NUnit.Framework;

namespace Kaleida.UnitTests.Model.Runtime
{
	[TestFixture]
    public class ScriptCompilerTests
    {
        private readonly OperationDefinition[] emptyCommandList = new OperationDefinition[0];
        private readonly string[] emptyCommentList = new string[0];
        private readonly DirectiveDefinition[] emptyDirectiveList = new DirectiveDefinition[0];

        [Test]
        public void TestIfOrderingNotExplicitUseRoundRobin()
        {
            var scriptDefinition = new ScriptDefinition(emptyDirectiveList, emptyCommandList, emptyCommentList);
            var script = new ScriptCompiler().Build(scriptDefinition, "MyScript");

            Assert.AreEqual(typeof(RoundRobinOperationOrder), script.Root.ScriptModule.ExecutionOrder.GetType());
        }

        [Test]
        public void TestCanSetOrderingFromDefinitionWithSoleDirective()
        {
            var directives = new[] {new DirectiveDefinition("order", new[] {"random"})};
            var scriptDefinition = new ScriptDefinition(directives, emptyCommandList, emptyCommentList);
            var script = new ScriptCompiler().Build(scriptDefinition, "MyScript");

            Assert.AreEqual(typeof(RandomOperationOrder), script.Root.ScriptModule.ExecutionOrder.GetType());
        }

        [Test]
        public void TestCanSetOrderingWhenConflictingDirectivesFound()
        {
            var directives = new[] { new DirectiveDefinition("order", new[] { "round-robin" }), new DirectiveDefinition("order", new[] { "random" }) };
            var scriptDefinition = new ScriptDefinition(directives, emptyCommandList, emptyCommentList);
            var script = new ScriptCompiler().Build(scriptDefinition, "MyScript");

            Assert.AreEqual(typeof(RandomOperationOrder), script.Root.ScriptModule.ExecutionOrder.GetType(), "The last directive encountered is used");
        }

        [Test]
        public void TestCanSetUsingOldFrequencyDirective()
        {
            var scriptDefinition = new ScriptDefinition(new[] { new DirectiveDefinition("poll-frequency", new[]{"1500"}) }, emptyCommandList, emptyCommentList);
            var script = new ScriptCompiler().Build(scriptDefinition, "MyScript");

            Assert.AreEqual(1500, script.Root.ScriptModule.PollInterval.Value);
            Assert.AreEqual("ms", script.Root.ScriptModule.PollInterval.Suffix);
        }

        [Test]
        public void TestCanSetInterval()
        {
            var scriptDefinition = new ScriptDefinition(new[] { new DirectiveDefinition("poll-interval", new[] { "1500" }) }, emptyCommandList, emptyCommentList);
            var script = new ScriptCompiler().Build(scriptDefinition, "MyScript");

            Assert.AreEqual(1500, script.Root.ScriptModule.PollInterval.Value);
            Assert.AreEqual("ms", script.Root.ScriptModule.PollInterval.Suffix);
        }


        [Test]
        public void TestPollIntervalIs1000IfNotExplicitlySet()
        {
            var scriptDefinition = new ScriptDefinition(emptyDirectiveList, emptyCommandList, emptyCommentList);
            var script = new ScriptCompiler().Build(scriptDefinition, "MyScript");

            Assert.AreEqual("1000 ms", script.Root.ScriptModule.PollInterval.ToString());
        }

        [Test]
        public void TestCommandNamesAreResolvedAndAssembled()
        {
            var commands = new[]
                               {
                                   new OperationDefinition(new ActionDefinition("my-command"), new ActionDefinition("my-handler")),
                                   new OperationDefinition(new ActionDefinition("my-other-command", new[] {"arg1", "arg2"}), new ActionDefinition("my-other-handler", new[] {"a"}))
                               };
            var scriptDefinition = new ScriptDefinition(emptyDirectiveList, commands, emptyCommentList);
            
            var mockFactory = new MockcommandFactory();
            var builder = new ScriptCompiler(mockFactory, mockFactory);
            var script = builder.Build(scriptDefinition, "MyScript");

            Assert.AreEqual(2, script.Root.ScriptModule.Operations.Count);
            Assert.AreEqual("DummyPreparedRequest and DummyResponseHandler", script.Root.ScriptModule.Operations[0].Description);
            Assert.AreEqual("DummyPreparedRequest and DummyResponseHandler", script.Root.ScriptModule.Operations[1].Description);
        }

		[Test]
		public void TestDefaultNaming()
		{
			var mockFactory = new MockcommandFactory();

			var op1 = new OperationDefinition(new ActionDefinition("action-1"), new ActionDefinition("none"));
			var op2 = new OperationDefinition(new ActionDefinition("action-2"), new ActionDefinition("none"));
			var op3 = new OperationDefinition(new ActionDefinition("action-3"), new ActionDefinition("none"));

			var childScript1 = new ScriptDefinition(emptyDirectiveList, new[] {op1, op2}, emptyCommentList);
			var childScript2 = new ScriptDefinition(emptyDirectiveList, new[] {op3}, emptyCommentList);
			var rootGroup = new ScriptDefinition(new[]{childScript1, childScript2});

			var script = new ScriptCompiler(mockFactory, mockFactory).Build(rootGroup, "MyScript");

			Assert.AreEqual(true, script.Root.IsGroup);
			Assert.AreEqual(2, script.Root.ScriptGroup.Children.Count);
			Assert.AreEqual(true, script.Root.ScriptGroup.Children[0].IsLeaf);
			Assert.AreEqual("MyScript-1", script.Root.ScriptGroup.Children[0].ScriptModule.Name);
			Assert.AreEqual(2, script.Root.ScriptGroup.Children[0].ScriptModule.Operations.Count);
			Assert.AreEqual(true, script.Root.ScriptGroup.Children[1].IsLeaf);
			Assert.AreEqual("MyScript-2", script.Root.ScriptGroup.Children[1].ScriptModule.Name);
			Assert.AreEqual(1, script.Root.ScriptGroup.Children[1].ScriptModule.Operations.Count);
			
		}

		[Test]
		public void TestCanSetColourStrategyUsingColoursDirective()
		{
			var scriptDefinition = new ScriptDefinition(new[] { new DirectiveDefinition("colours", new[] { "0 failures green" }) }, emptyCommandList, emptyCommentList);
			var script = new ScriptCompiler().Build(scriptDefinition, "MyScript");

			Assert.AreEqual("Not monitoring: Gray, 0 failures: Green, otherwise White", script.Root.ScriptModule.ColourationStrategy.Description);
		}

		[Test]
		public void TestCanSetColourStrategyUsingColorsDirective()
		{
            var scriptDefinition = new ScriptDefinition(new[] { new DirectiveDefinition("colors", new[] { "0 failures green" }) }, emptyCommandList, emptyCommentList);
			var script = new ScriptCompiler().Build(scriptDefinition, "MyScript");

			Assert.AreEqual("Not monitoring: Gray, 0 failures: Green, otherwise White", script.Root.ScriptModule.ColourationStrategy.Description);
		}

		[Test]
        [ExpectedException(typeof(InvalidOperationException), ExpectedMessage = "Unknown metric 'foos'. Must be 'failures' or 'success-rate'")]
		public void TestExceptionIsThrownIfInvalidColourMetric()
		{
            var scriptDefinition = new ScriptDefinition(new[] { new DirectiveDefinition("colours", new[] { "0 foos green" }) }, emptyCommandList, emptyCommentList);
			new ScriptCompiler().Build(scriptDefinition, "MyScript");
		}

        private class MockcommandFactory : IPreparedRequestFactory, IResponseHandlerFactory
        {
            PreparedRequest IPreparedRequestFactory.Build(string name, IList<string> arguments)
            {
                return new DummyPreparedRequest();
            }

            ResponseHandler IResponseHandlerFactory.Build(string name, IList<string> arguments)
            {
                return new DummyResponseHandler();
            }
        }

        private class DummyPreparedRequest : PreparedRequest
        {
            public override string Description
            {
                get { return "DummyPreparedRequest"; }
            }

            public override object GetResponse()
            {
                return 1;
            }
        }

        private class DummyResponseHandler : ResponseHandler
        {
            public override string Description
            {
                get { return "DummyResponseHandler"; }
            }

            public override string ProcessResponse(object response)
            {
                return "";
            }
        }
    }
}
